/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.code;

import java.util.BitSet;

import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstInteger;
import com.android.dx.rop.cst.CstKnownNull;
import com.android.dx.rop.cst.CstLiteral64;
import com.android.dx.rop.cst.CstLiteralBits;
import com.android.dx.rop.cst.CstString;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.Hex;

/**
 * Base class for all instruction format handlers. Instruction format handlers
 * know how to translate {@link DalvInsn} instances into streams of code units,
 * as well as human-oriented listing strings representing such translations.
 */
public abstract class InsnFormat {

	/**
	 * flag to enable/disable the new extended opcode formats; meant as a
	 * temporary measure until VM support for the salient opcodes is added.
	 * TODO: Remove this declaration when the VM can deal.
	 */
	public static boolean ALLOW_EXTENDED_OPCODES = true;

	/**
	 * Returns the string form, suitable for inclusion in a listing dump, of the
	 * given instruction. The instruction must be of this instance's format for
	 * proper operation.
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction
	 * @param noteIndices
	 *            whether to include an explicit notation of constant pool
	 *            indices
	 * @return {@code non-null;} the string form
	 */
	public final String listingString(DalvInsn insn, boolean noteIndices) {
		String op = insn.getOpcode().getName();
		String arg = insnArgString(insn);
		String comment = insnCommentString(insn, noteIndices);
		StringBuilder sb = new StringBuilder(100);

		sb.append(op);

		if (arg.length() != 0) {
			sb.append(' ');
			sb.append(arg);
		}

		if (comment.length() != 0) {
			sb.append(" // ");
			sb.append(comment);
		}

		return sb.toString();
	}

	/**
	 * Returns the string form of the arguments to the given instruction. The
	 * instruction must be of this instance's format. If the instruction has no
	 * arguments, then the result should be {@code ""}, not {@code null}.
	 * <p>
	 * Subclasses must override this method.
	 * </p>
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction
	 * @return {@code non-null;} the string form
	 */
	public abstract String insnArgString(DalvInsn insn);

	/**
	 * Returns the associated comment for the given instruction, if any. The
	 * instruction must be of this instance's format. If the instruction has no
	 * comment, then the result should be {@code ""}, not {@code null}.
	 * <p>
	 * Subclasses must override this method.
	 * </p>
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction
	 * @param noteIndices
	 *            whether to include an explicit notation of constant pool
	 *            indices
	 * @return {@code non-null;} the string form
	 */
	public abstract String insnCommentString(DalvInsn insn, boolean noteIndices);

	/**
	 * Gets the code size of instructions that use this format. The size is a
	 * number of 16-bit code units, not bytes. This should throw an exception if
	 * this format is of variable size.
	 * 
	 * @return {@code >= 0;} the instruction length in 16-bit code units
	 */
	public abstract int codeSize();

	/**
	 * Returns whether or not the given instruction's arguments will fit in this
	 * instance's format. This includes such things as counting register
	 * arguments, checking register ranges, and making sure that additional
	 * arguments are of appropriate types and are in-range. If this format has a
	 * branch target but the instruction's branch offset is unknown, this method
	 * will simply not check the offset.
	 * <p>
	 * Subclasses must override this method.
	 * </p>
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction to check
	 * @return {@code true} iff the instruction's arguments are appropriate for
	 *         this instance, or {@code false} if not
	 */
	public abstract boolean isCompatible(DalvInsn insn);

	/**
	 * Returns which of a given instruction's registers will fit in this
	 * instance's format.
	 * <p>
	 * The default implementation of this method always returns an empty BitSet.
	 * Subclasses must override this method if they have registers.
	 * </p>
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction to check
	 * @return {@code non-null;} a BitSet flagging registers in the register
	 *         list that are compatible to this format
	 */
	public BitSet compatibleRegs(DalvInsn insn) {
		return new BitSet();
	}

	/**
	 * Returns whether or not the given instruction's branch offset will fit in
	 * this instance's format. This always returns {@code false} for formats
	 * that don't include a branch offset.
	 * <p>
	 * The default implementation of this method always returns {@code false}.
	 * Subclasses must override this method if they include branch offsets.
	 * </p>
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction to check
	 * @return {@code true} iff the instruction's branch offset is appropriate
	 *         for this instance, or {@code false} if not
	 */
	public boolean branchFits(TargetInsn insn) {
		return false;
	}

	/**
	 * Writes the code units for the given instruction to the given output
	 * destination. The instruction must be of this instance's format.
	 * <p>
	 * Subclasses must override this method.
	 * </p>
	 * 
	 * @param out
	 *            {@code non-null;} the output destination to write to
	 * @param insn
	 *            {@code non-null;} the instruction to write
	 */
	public abstract void writeTo(AnnotatedOutput out, DalvInsn insn);

	/**
	 * Helper method to return a register list string.
	 * 
	 * @param list
	 *            {@code non-null;} the list of registers
	 * @return {@code non-null;} the string form
	 */
	protected static String regListString(RegisterSpecList list) {
		int sz = list.size();
		StringBuffer sb = new StringBuffer(sz * 5 + 2);

		sb.append('{');

		for (int i = 0; i < sz; i++) {
			if (i != 0) {
				sb.append(", ");
			}
			sb.append(list.get(i).regString());
		}

		sb.append('}');

		return sb.toString();
	}

	/**
	 * Helper method to return a register range string.
	 * 
	 * @param list
	 *            {@code non-null;} the list of registers (which must be
	 *            sequential)
	 * @return {@code non-null;} the string form
	 */
	protected static String regRangeString(RegisterSpecList list) {
		int size = list.size();
		StringBuilder sb = new StringBuilder(30);

		sb.append("{");

		switch (size) {
		case 0: {
			// Nothing to do.
			break;
		}
		case 1: {
			sb.append(list.get(0).regString());
			break;
		}
		default: {
			RegisterSpec lastReg = list.get(size - 1);
			if (lastReg.getCategory() == 2) {
				/*
				 * Add one to properly represent a list-final category-2
				 * register.
				 */
				lastReg = lastReg.withOffset(1);
			}

			sb.append(list.get(0).regString());
			sb.append("..");
			sb.append(lastReg.regString());
		}
		}

		sb.append("}");

		return sb.toString();
	}

	/**
	 * Helper method to return a literal bits argument string.
	 * 
	 * @param value
	 *            the value
	 * @return {@code non-null;} the string form
	 */
	protected static String literalBitsString(CstLiteralBits value) {
		StringBuffer sb = new StringBuffer(100);

		sb.append('#');

		if (value instanceof CstKnownNull) {
			sb.append("null");
		} else {
			sb.append(value.typeName());
			sb.append(' ');
			sb.append(value.toHuman());
		}

		return sb.toString();
	}

	/**
	 * Helper method to return a literal bits comment string.
	 * 
	 * @param value
	 *            the value
	 * @param width
	 *            the width of the constant, in bits (used for displaying the
	 *            uninterpreted bits; one of: {@code 4 8 16 32 64}
	 * @return {@code non-null;} the comment
	 */
	protected static String literalBitsComment(CstLiteralBits value, int width) {
		StringBuffer sb = new StringBuffer(20);

		sb.append("#");

		long bits;

		if (value instanceof CstLiteral64) {
			bits = ((CstLiteral64) value).getLongBits();
		} else {
			bits = value.getIntBits();
		}

		switch (width) {
		case 4:
			sb.append(Hex.uNibble((int) bits));
			break;
		case 8:
			sb.append(Hex.u1((int) bits));
			break;
		case 16:
			sb.append(Hex.u2((int) bits));
			break;
		case 32:
			sb.append(Hex.u4((int) bits));
			break;
		case 64:
			sb.append(Hex.u8(bits));
			break;
		default: {
			throw new RuntimeException("shouldn't happen");
		}
		}

		return sb.toString();
	}

	/**
	 * Helper method to return a branch address string.
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction in question
	 * @return {@code non-null;} the string form of the instruction's branch
	 *         target
	 */
	protected static String branchString(DalvInsn insn) {
		TargetInsn ti = (TargetInsn) insn;
		int address = ti.getTargetAddress();

		return (address == (char) address) ? Hex.u2(address) : Hex.u4(address);
	}

	/**
	 * Helper method to return the comment for a branch.
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction in question
	 * @return {@code non-null;} the comment
	 */
	protected static String branchComment(DalvInsn insn) {
		TargetInsn ti = (TargetInsn) insn;
		int offset = ti.getTargetOffset();

		return (offset == (short) offset) ? Hex.s2(offset) : Hex.s4(offset);
	}

	/**
	 * Helper method to return the constant string for a {@link CstInsn} in
	 * human form.
	 * 
	 * @param insn
	 *            {@code non-null;} a constant-bearing instruction
	 * @return {@code non-null;} the human string form of the contained constant
	 */
	protected static String cstString(DalvInsn insn) {
		CstInsn ci = (CstInsn) insn;
		Constant cst = ci.getConstant();

		return cst instanceof CstString ? ((CstString) cst).toQuoted() : cst
				.toHuman();
	}

	/**
	 * Helper method to return an instruction comment for a constant.
	 * 
	 * @param insn
	 *            {@code non-null;} a constant-bearing instruction
	 * @return {@code non-null;} comment string representing the constant
	 */
	protected static String cstComment(DalvInsn insn) {
		CstInsn ci = (CstInsn) insn;

		if (!ci.hasIndex()) {
			return "";
		}

		StringBuilder sb = new StringBuilder(20);
		int index = ci.getIndex();

		sb.append(ci.getConstant().typeName());
		sb.append('@');

		if (index < 65536) {
			sb.append(Hex.u2(index));
		} else {
			sb.append(Hex.u4(index));
		}

		return sb.toString();
	}

	/**
	 * Helper method to determine if a signed int value fits in a nibble.
	 * 
	 * @param value
	 *            the value in question
	 * @return {@code true} iff it's in the range -8..+7
	 */
	protected static boolean signedFitsInNibble(int value) {
		return (value >= -8) && (value <= 7);
	}

	/**
	 * Helper method to determine if an unsigned int value fits in a nibble.
	 * 
	 * @param value
	 *            the value in question
	 * @return {@code true} iff it's in the range 0..0xf
	 */
	protected static boolean unsignedFitsInNibble(int value) {
		return value == (value & 0xf);
	}

	/**
	 * Helper method to determine if a signed int value fits in a byte.
	 * 
	 * @param value
	 *            the value in question
	 * @return {@code true} iff it's in the range -0x80..+0x7f
	 */
	protected static boolean signedFitsInByte(int value) {
		return (byte) value == value;
	}

	/**
	 * Helper method to determine if an unsigned int value fits in a byte.
	 * 
	 * @param value
	 *            the value in question
	 * @return {@code true} iff it's in the range 0..0xff
	 */
	protected static boolean unsignedFitsInByte(int value) {
		return value == (value & 0xff);
	}

	/**
	 * Helper method to determine if a signed int value fits in a short.
	 * 
	 * @param value
	 *            the value in question
	 * @return {@code true} iff it's in the range -0x8000..+0x7fff
	 */
	protected static boolean signedFitsInShort(int value) {
		return (short) value == value;
	}

	/**
	 * Helper method to determine if an unsigned int value fits in a short.
	 * 
	 * @param value
	 *            the value in question
	 * @return {@code true} iff it's in the range 0..0xffff
	 */
	protected static boolean unsignedFitsInShort(int value) {
		return value == (value & 0xffff);
	}

	/**
	 * Helper method to determine if a list of registers are sequential,
	 * including degenerate cases for empty or single-element lists.
	 * 
	 * @param list
	 *            {@code non-null;} the list of registers
	 * @return {@code true} iff the list is sequentially ordered
	 */
	protected static boolean isRegListSequential(RegisterSpecList list) {
		int sz = list.size();

		if (sz < 2) {
			return true;
		}

		int first = list.get(0).getReg();
		int next = first;

		for (int i = 0; i < sz; i++) {
			RegisterSpec one = list.get(i);
			if (one.getReg() != next) {
				return false;
			}
			next += one.getCategory();
		}

		return true;
	}

	/**
	 * Helper method to extract the callout-argument index from an appropriate
	 * instruction.
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction
	 * @return {@code >= 0;} the callout argument index
	 */
	protected static int argIndex(DalvInsn insn) {
		int arg = ((CstInteger) ((CstInsn) insn).getConstant()).getValue();

		if (arg < 0) {
			throw new IllegalArgumentException("bogus insn");
		}

		return arg;
	}

	/**
	 * Helper method to combine an opcode and a second byte of data into the
	 * appropriate form for emitting into a code buffer.
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction containing the opcode
	 * @param arg
	 *            {@code 0..255;} arbitrary other byte value
	 * @return combined value
	 */
	protected static short opcodeUnit(DalvInsn insn, int arg) {
		if ((arg & 0xff) != arg) {
			throw new IllegalArgumentException("arg out of range 0..255");
		}

		int opcode = insn.getOpcode().getOpcode();

		if ((opcode & 0xff) != opcode) {
			throw new IllegalArgumentException("opcode out of range 0..255");
		}

		return (short) (opcode | (arg << 8));
	}

	/**
	 * Helper method to get an extended (16-bit) opcode out of an instruction,
	 * returning it as a code unit. The opcode <i>must</i> be an extended
	 * opcode.
	 * 
	 * @param insn
	 *            {@code non-null;} the instruction containing the extended
	 *            opcode
	 * @return the opcode as a code unit
	 */
	protected static short opcodeUnit(DalvInsn insn) {
		int opcode = insn.getOpcode().getOpcode();

		if ((opcode < 0xff) || (opcode > 0xffff)) {
			throw new IllegalArgumentException(
					"extended opcode out of range 255..65535");
		}

		return (short) opcode;
	}

	/**
	 * Helper method to combine two bytes into a code unit.
	 * 
	 * @param low
	 *            {@code 0..255;} low byte
	 * @param high
	 *            {@code 0..255;} high byte
	 * @return combined value
	 */
	protected static short codeUnit(int low, int high) {
		if ((low & 0xff) != low) {
			throw new IllegalArgumentException("low out of range 0..255");
		}

		if ((high & 0xff) != high) {
			throw new IllegalArgumentException("high out of range 0..255");
		}

		return (short) (low | (high << 8));
	}

	/**
	 * Helper method to combine four nibbles into a code unit.
	 * 
	 * @param n0
	 *            {@code 0..15;} low nibble
	 * @param n1
	 *            {@code 0..15;} medium-low nibble
	 * @param n2
	 *            {@code 0..15;} medium-high nibble
	 * @param n3
	 *            {@code 0..15;} high nibble
	 * @return combined value
	 */
	protected static short codeUnit(int n0, int n1, int n2, int n3) {
		if ((n0 & 0xf) != n0) {
			throw new IllegalArgumentException("n0 out of range 0..15");
		}

		if ((n1 & 0xf) != n1) {
			throw new IllegalArgumentException("n1 out of range 0..15");
		}

		if ((n2 & 0xf) != n2) {
			throw new IllegalArgumentException("n2 out of range 0..15");
		}

		if ((n3 & 0xf) != n3) {
			throw new IllegalArgumentException("n3 out of range 0..15");
		}

		return (short) (n0 | (n1 << 4) | (n2 << 8) | (n3 << 12));
	}

	/**
	 * Helper method to combine two nibbles into a byte.
	 * 
	 * @param low
	 *            {@code 0..15;} low nibble
	 * @param high
	 *            {@code 0..15;} high nibble
	 * @return {@code 0..255;} combined value
	 */
	protected static int makeByte(int low, int high) {
		if ((low & 0xf) != low) {
			throw new IllegalArgumentException("low out of range 0..15");
		}

		if ((high & 0xf) != high) {
			throw new IllegalArgumentException("high out of range 0..15");
		}

		return low | (high << 4);
	}

	/**
	 * Writes one code unit to the given output destination.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0) {
		out.writeShort(c0);
	}

	/**
	 * Writes two code units to the given output destination.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0, short c1) {
		out.writeShort(c0);
		out.writeShort(c1);
	}

	/**
	 * Writes three code units to the given output destination.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1
	 *            code unit to write
	 * @param c2
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0, short c1,
			short c2) {
		out.writeShort(c0);
		out.writeShort(c1);
		out.writeShort(c2);
	}

	/**
	 * Writes four code units to the given output destination.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1
	 *            code unit to write
	 * @param c2
	 *            code unit to write
	 * @param c3
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0, short c1,
			short c2, short c3) {
		out.writeShort(c0);
		out.writeShort(c1);
		out.writeShort(c2);
		out.writeShort(c3);
	}

	/**
	 * Writes five code units to the given output destination.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1
	 *            code unit to write
	 * @param c2
	 *            code unit to write
	 * @param c3
	 *            code unit to write
	 * @param c4
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0, short c1,
			short c2, short c3, short c4) {
		out.writeShort(c0);
		out.writeShort(c1);
		out.writeShort(c2);
		out.writeShort(c3);
		out.writeShort(c4);
	}

	/**
	 * Writes three code units to the given output destination, where the second
	 * and third are represented as single <code>int</code> and emitted in
	 * little-endian order.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1c2
	 *            code unit pair to write
	 */
	protected static void write(AnnotatedOutput out, short c0, int c1c2) {
		write(out, c0, (short) c1c2, (short) (c1c2 >> 16));
	}

	/**
	 * Writes four code units to the given output destination, where the second
	 * and third are represented as single <code>int</code> and emitted in
	 * little-endian order.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1c2
	 *            code unit pair to write
	 * @param c3
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0, int c1c2,
			short c3) {
		write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3);
	}

	/**
	 * Writes five code units to the given output destination, where the second
	 * and third are represented as single <code>int</code> and emitted in
	 * little-endian order.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1c2
	 *            code unit pair to write
	 * @param c3
	 *            code unit to write
	 * @param c4
	 *            code unit to write
	 */
	protected static void write(AnnotatedOutput out, short c0, int c1c2,
			short c3, short c4) {
		write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3, c4);
	}

	/**
	 * Writes five code units to the given output destination, where the second
	 * through fifth are represented as single <code>long</code> and emitted in
	 * little-endian order.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 * @param c0
	 *            code unit to write
	 * @param c1c2c3c4
	 *            code unit quad to write
	 */
	protected static void write(AnnotatedOutput out, short c0, long c1c2c3c4) {
		write(out, c0, (short) c1c2c3c4, (short) (c1c2c3c4 >> 16),
				(short) (c1c2c3c4 >> 32), (short) (c1c2c3c4 >> 48));
	}
}
